package controllers;


import boardGenerator.Pair;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.net.URI;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import javax.swing.AbstractButton;
import models.Configuration;
import models.Jugador;
import models.Partida;
import models.TopTen;
import view.*;

/**
 * Clase SudokuController implementa el control del juego a partir
 * de la logica (models.Partida) y a partir de la interfaz Grafica
 * (view.IMainView). Implementa un Observer y un ActionListener
 * Esta clase es la encargada de observar a la partida y a la vista y 
 * responde antes sus notificaciones de cambios (metodo update) , a su vez esta misma clase 
 * implementará el acionListener para los botones de las interfaces 
 * (metodo actionPerformed) a travez del cual ejecutara los diferentes comandos solicitados
 */
public class SudokuController implements Observer,ActionListener{
    private Partida partida;
    private IMainView vista;
    private Configuration configuration;
    private boolean pista;
    private static SudokuController instance = null;
    private boolean gameStarted;
    private boolean vistaAlternativa;
    private boolean resueltoPorSistema;
    private TopTen topTen;
    private Jugador player;
    
    /**
     * Obtiene la instancia unica del Controlador
     * nota: la instancia debe ser creada una vez
     * con createInstance()
     * @return SudokuController asosiado a la partida Actual
     */
    public static SudokuController getInstance(){
        if (instance == null){
            throw new NullPointerException();
        }
        return instance;        
    }
    
    /**
     * Crea la instancia unica del controlador
     * @param modelo Modelo de Partida Sudoku
     */
    public synchronized static void createInstance(Partida modelo){
        if (instance == null){
            instance = new SudokuController(modelo);
        }
    }
    
    /**
     * Constructor privado de la clase
     * @param modelo Modelo partida Sudoku
     */
    private SudokuController(Partida modelo) {        
        this.partida = modelo;
        this.configuration = modelo.getConfig();
        this.partida.addObserver(this);        
        this.partida.getCronometro().addObserver(this);
        this.pista = false;
        this.gameStarted = false;
        this.resueltoPorSistema = false;
        this.topTen = new TopTen();
    }
    
    /**
     * Asosia una vista con el Controlador
     * @param view vista concreta del juego
     */
    public void setView(IMainView view){
        this.vista = view;
        this.vista.addObserver(this);
        this.vistaAlternativa = false;
    }

    /**
     * Obtiene la configuracion de la partida actual
     * @return gameconfiguration
     */
    public Configuration getConfiguration(){
        return this.configuration;
    }
    
    /**
     * Accion del Observer, cuando un elemnto observado
     * cambia y notifica al observador ejecuta este metodo
     * @param o clase observable
     * @param arg1 argumentos de la llamada
     */
    @Override
    public void update(Observable o, Object arg1) {        
        try {
            String tmp = arg1.toString();
            switch (tmp) {
                case "CHANGE_POINTS" : actualizarPuntuacion();break;
                case "TIME_CHANGED" : actualizarTiempo();break;
                case "NUMBERCOUNT_CHANGED" : actualizaContador();break;
            }            
        }catch (Exception ex){}
        try {
            Pair<Pair<Integer,Integer>,Integer> tmp2 = (Pair<Pair<Integer,Integer>,Integer>) arg1;
            cambiaValor(tmp2);            
        } catch (Exception ex2){}
    }
    
    /**
     * actionPerformed del Controller como listener de botones
     * @param arg ActionCommand
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        try{
            String arg = ((AbstractButton)e.getSource()).getActionCommand();
            if (arg.equals("CHECK_GAME")){
                this.verificarJugada();            
            }        
            if (arg.equals("START_GAME")){
                this.nuevoJuego();
            }
            if (arg.equals("SOLVE_GAME")){            
                this.resolver();
            }
            if (arg.equals("DIF_HARD")){
                this.dificultadDificil();
            }
            if (arg.equals("DIF_MODERATE")){
                this.dificultadMedia();
            }
            if (arg.equals("DIF_EASY")){
                this.dificultadFacil();
            }
            if (arg.equals("GET_HINT")){
                this.sugerirJugada();
            }
            if (arg.equals("RESET_GAME")){
                this.resetTablero();
            }
            if (arg.equals("STOP_GAME")){
                this.detenerJuego();
            }
            if (arg.equals("SUDOKU_WEB")){
                this.irAWeb();
            }
            if (arg.equals("OTHER_VIEW")){
                this.changeView();
            }
            if (arg.equals("EXIT")){
                this.systemExit();
            }
            if (arg.equals("TOP_TEN")){
                this.showTopTen();
            }
            if (arg.equals("CONFIG")){
                this.showConfig();
            }
            if (arg.equals("RULES")){
                this.showRules();
            }            
        } catch (Exception ex){
            System.out.println("ERROR: Mapear AbstractButton a Listener SudokuController");
            ex.printStackTrace();
        }
    }
     
    /**
     * Metodos Privados de la Clase
     */
    
    /**
     * Inicia un nuevo Juego
     */
    private void nuevoJuego(){
        if (this.gameStarted){
            vista.showWarningMessage("Detener Partida Actual");
            return;
        }        
        vista.habilitarTablero();        
        partida.nuevaPartida();        
        this.actualizarMatriz();
        vista.habilitarControles(true);
        this.gameStarted = true;
        this.pista = false;
    }
    
    /**
     * Reinicia el tablero con el problema original
     * con la puntuacion y tiempo actual
     */
    private void resetTablero(){
        if (!this.gameStarted){
            vista.showWarningMessage("Partida No Inciada");
            return;
        }
        if (vista.showConfirmationMessage("¿Reiniciar Tablero?")){
            this.partida.resetBoard();
            vista.setMatriz(partida.getTablero());
        }
    }
    
    /**
     * Cambia Dificulta Facil
     */
    private void dificultadFacil() {        
        configuration.setDifficulty("EASY");
    }
    
    /**
     * Cambia Dificulta Media
     */
    private void dificultadMedia() {
        configuration.setDifficulty("MODERATE");
    }
    
    /**
     * Cambia Dificulta Dificil
     */
    private void dificultadDificil() {
        configuration.setDifficulty("HARD");
    }
    
    /**
     * Cambia el valor de una celda en la matriz del juego
     * Si es una pista lo que se pasa como parametro tambien
     * actualiza la vista grafica
     * @param celda 
     */
    private void cambiaValor(Pair<Pair<Integer,Integer>,Integer> celda){
        if (pista){
            vista.setPista(celda.getFirst().getFirst(),celda.getFirst().getSecond(),celda.getSecond());            
        }
        if (celda.getSecond() == 0){
            partida.BorrarValor(celda.getFirst().getFirst(),celda.getFirst().getSecond());
        } else {
            partida.InsertarValor(celda.getFirst().getFirst(),celda.getFirst().getSecond(),celda.getSecond());
        }
    }
    
    /**
     * Permite Obtener una pista y mostrarla por pantalla
     */
    private void sugerirJugada(){
        if (!this.gameStarted){
            vista.showWarningMessage("Partida No Inciada");
            return;
        }
        this.pista = true;
        partida.sugerirJugada();
        this.pista = false;        
    }

    /**
     * Verifica si el juego actual es correcto
     * @return true/false:correcto,incorrecto
     */
    private boolean verificarJugada() {
        if (!this.gameStarted){
            vista.showWarningMessage("Partida No Inciada");
            return false;
        }
        List<Pair<Integer,Integer>> lista = partida.verificarJugada();
        for (Pair<Integer, Integer> pair : lista) {
            vista.setWrong(pair.getFirst(),pair.getSecond());
        }
        return lista.isEmpty();
    }
    
    /**
     * Permite detener la partida actual
     * siempre y cuando el juego finalize
     * correctamente, agregar la puntuacion
     * actual al topten de jugadas
     */
    private void detener(){
        if (!this.gameStarted) return;
        if (verificarJugada()){
            partida.getCronometro().pararTiempo();
            vista.habilitarControles(false);
            vista.deshabilitarTablero();
            this.gameStarted = false;
            if (!resueltoPorSistema){
                vista.showInfoMessage("Felicitaciones a finalizado el juego Correctamente");
                configuration.setNamePlayer(vista.getPlayerName());
                player = new Jugador(configuration,partida);
                topTen.addJugada(player);                
            }
            this.resueltoPorSistema = false;
        }
    }
    
    /**
     * Detiene el juego actual en cualquier momento dado
     */
    private void detenerJuego(){
        if (!this.gameStarted){
            vista.showWarningMessage("Partida No Inciada");
            return;
        }
        if (vista.showConfirmationMessage("¿Desea Terminar la Partida Actual?")){
            partida.getCronometro().pararTiempo();
            vista.deshabilitarTablero();
            vista.habilitarControles(false);        
            this.gameStarted = false;
        }
    }
    
    /**
     * Resuelve la partida actual en la logica
     */
    private void resolver() {
        if (!this.gameStarted){
            vista.showWarningMessage("Partida No Inciada");
            return;
        }
        if (vista.showConfirmationMessage("¿Desea Resolver Partida Actual?")){
            this.resueltoPorSistema = true;
            partida.resolver();
            this.actualizarMatriz();
        }
    }
    
    /**
     * Mete la matriz de la partida en la vista actual
     */
    private void actualizarMatriz(){
            vista.setMatriz(partida.getTablero());
    }
    
    /**
     * Actualiza el contador de numeros restantes
     */
    private void actualizaContador(){
        vista.refreshContador(partida.getCantNumbersToPut()+"");
        if (partida.getCantNumbersToPut() == 0){
            this.detener();
        }
    }
    
    /**
     * Actualiza el contador de puntuacion
     */
    private void actualizarPuntuacion(){
        vista.refreshPuntuacion(partida.getPuntuacion()+"");
    }
    
    /**
     * Actualiza el contador de tiempo transcurrido
     */
    private void actualizarTiempo(){
        vista.refreshTime(partida.getTiempoPartida());
        partida.calcularPuntuacion();
    }
    
    /**
     * Cambia de Vista
     */
    private void changeView(){
        if (this.gameStarted) {
            vista.showWarningMessage("Primero detener Partida Actual");
            return;
        }
        if (!vistaAlternativa){
            IMainView tmp = new AlternativeView();
            tmp.addObserver(this);
            configuration.setNamePlayer(vista.getPlayerName());
            vista.dispose();
            vista = tmp;
            vistaAlternativa = true;
        } else {
            IMainView tmp = new MainView();
            tmp.addObserver(this);
            tmp.setPlayerName(configuration.getNamePlayer());
            vista.dispose();
            vista = tmp;
            vistaAlternativa = false;
        }   
    }

    /**
     * Finaliza el Programa
     */
    private void systemExit() {
        if (vista.showConfirmationMessage("Salir de Sudoku?")){
            System.exit(0);
        }
    }
    
    /**
     * Muestra la ventana top-10 y evita la interaccion
     * con el tablero hasta que se cierre dicha ventana
     */
    private void showTopTen(){
        if (this.gameStarted) {
            vista.showWarningMessage("Primero detener Partida Actual");
            return;
        }
        vista.disable(true);
        TopTenView tt = new TopTenView(topTen);
        tt.addWindowListener(new WindowAdapter(){
        	public void windowClosed(WindowEvent e){
                    vista.disable(false);
                    vista.requestFocus();
                }
            });        
    }
    
    /**
     * Muestra el panel de configuracion y obtiene
     * los datos ingresados por el usuario
     */
    private void showConfig(){
        if (this.gameStarted) {
            vista.showWarningMessage("Primero detener Partida Actual");
            return;
        }
        vista.disable(true);
        ConfigView tt = new ConfigView(configuration);
        tt.addWindowListener(new WindowAdapter(){
        	public void windowClosed(WindowEvent e){
                    vista.disable(false);                    
                    vista.requestFocus();
                }
            });
    }
    
    /**
     * Muesta la ventana de Reglas
     */
    private void showRules(){
        if (this.gameStarted) {
            vista.showWarningMessage("Primero detener Partida Actual");
            return;
        }
        vista.disable(true);
        RulesView rr = new RulesView();
        rr.addWindowListener(new WindowAdapter(){
        	public void windowClosed(WindowEvent e){
                    vista.disable(false);                    
                    vista.requestFocus();
                }
        });
    }

    /**
     * Abre un navegador web con pagina de sudoku
     */
    private void irAWeb() {
        try {
            Desktop.getDesktop().browse(new URI("http://www.sudoku-solutions.com"));
        } catch (Exception ex) {
            System.out.println("No se puede abrir navegador");
        }
    }
}
